import net from 'net';
import {Lock} from './comiee.mjs';

class ClientComm {
    static _HOST = '192.168.1.101';
    static _PORT = 9999;
    static _ASYNC_PORT = 9998;

    _socket;
    #buffer = new Uint8Array(0);

    constructor(host, port) {
        this._socket = net.createConnection({
            host: host,
            port: port,
        }, () => console.log(`连接到：${host}:${port}`));

        this._socket.on('end', () => {
            console.log(`断开连接：${host}:${port}`);
        });

        this._socket.on('error', err => {
            console.error(`出错: ${err}`);
        });
    }

    close() {
        this._socket.end();
    }

    async #write(s) {
        return new Promise(resolve => {
            let writeData = () => {
                if (this._socket.write(s)) {
                    resolve();
                } else {
                    this._socket.once('drain', writeData);
                }
            };
            writeData();
        })
    }

    async _sendMsg(msg) {
        let len = `${msg.length}`;
        let n = `${len.length}`.padStart(5, '0');
        await this.#write(n);
        await this.#write(len);
        await this.#write(msg);
    }

    async #read(n) {
        return new Promise(resolve => {
            let readData = () => {
                if (this.#buffer.length >= n) {
                    let res = this.#buffer.slice(0, n).toString();
                    this.#buffer = this.#buffer.slice(n);
                    resolve(res);
                } else {
                    this._socket.once('data', data => {
                        this.#buffer = Buffer.concat([this.#buffer, data]);
                        readData();
                    });
                }
            };
            readData();
        })
    }

    async _recvMsg() {
        let n = await this.#read(5);
        let len = await this.#read(+n);
        let msg = await this.#read(+len);
        return msg.toString();
    }
}

class Listener extends ClientComm {
    #name;
    #lock = new Lock();
    #buffer = new Uint8Array(0);
    #cmdMap = new Map();

    constructor(name) {
        super(ClientComm._HOST, ClientComm._PORT);
        this.#name = name;
    }

    start() {
        let registerMsg = JSON.stringify({
            cmd: 'register',
            value: {
                name: this.#name,
                client_type: 'receiver',
            }
        });
        +this.#lock.with(async () => {
            try {
                await this._sendMsg(registerMsg);
                console.log(`向服务器注册监听器：${this.#name}`);
            } catch (e) {
                console.log(`向服务器注册监听器失败：${e}`)
            }
        });

        this._socket.on('data', async data => {
            this.#buffer = Buffer.concat([this.#buffer, data]);
            await this.#lock.with(async () => {
                await this.#listen();
            })
        })
    }

    async #listen() {
        if (this.#buffer.length < 5) return;
        let n = +this.#buffer.slice(0, 5).toString();
        if (this.#buffer.length < 5 + n) return;
        let len = +this.#buffer.slice(5, 5 + n).toString();
        if (this.#buffer.length < 5 + n + len) return;
        let msg = this.#buffer.slice(5 + n, 5 + n + len).toString();
        this.#buffer = this.#buffer.slice(5 + n + len);

        console.log(`客户端${this.#name}收到服务器消息：${msg}`);
        let {cmd, value} = JSON.parse(msg);
        if (!this.#cmdMap.has(cmd)) {
            throw `未注册的命令字${cmd}`;
        }
        let ret = JSON.stringify({
            cmd: 'result',
            value: await this.#cmdMap.get(cmd)(value),
        });
        console.log(`客户端${this.#name}向服务器回响应：${ret}`);
        await this._sendMsg(ret);
    }

    subscribe(cmd, fun) {
        this.#cmdMap.set(cmd, fun);
    }
}

export class Client extends ClientComm {
    #name;
    #lock = new Lock();
    #listener: Listener;

    constructor(name) {
        super(ClientComm._HOST, ClientComm._PORT);
        this.#name = name;
        this.#listener = new Listener(this.#name);
    }

    close() {
        super.close();
        this.#listener.close();
    }

    async #sendMessage(cmd, value) {
        let msg = JSON.stringify({
            cmd: cmd,
            value: value,
        });
        await this._sendMsg(msg);
        console.log(`客户端${this.#name}发送消息到服务器：${msg}`);
    }

    async #recvMessage() {
        let msg = await this._recvMsg();
        console.log(`客户端${this.#name}收到服务器回响应：${msg}`)
        let {cmd, value} = JSON.parse(msg);
        if (cmd !== 'result') {
            throw `接受到的消息命令字错误，预期：result，实际：${cmd}`
        }
        return value;
    }

    async register() {
        return await this.#lock.with(async () => {
            console.log(`向服务器注册客户端：${this.#name}`)
            await this.#sendMessage('register', {
                name: this.#name,
                client_type: 'sender',
            })
            return this;
        });
    }

    async send(cmd, value) {
        return await this.#lock.with(async () => {
            await this.#sendMessage(cmd, value);
            return await this.#recvMessage();
        });
    }

    startListen() {
        this.#listener.start();
    }

    subscribe(cmd, fun) {
        this.#listener.subscribe(cmd, fun);
    }
}

export class AsyncClient extends ClientComm {
    constructor() {
        super(ClientComm._HOST, ClientComm._ASYNC_PORT);
    }

    async connect(cmd) {
        await this.send(cmd);
        return this;
    }

    #messageEncode(obj) {
        if (obj.constructor === String) {
            return `str:${obj}`;
        } else if (obj.constructor === Object) {
            return `json:${JSON.stringify(obj)}`;
        } else {
            throw `不支持发送的类型：${typeof obj}`;
        }
    }

    #messageDecode(msg) {
        let [type, val] = msg.split(':', 2);
        switch (type) {
            case 'str':
                return val;
            case 'json':
                return JSON.parse(val);
            default:
                throw `不支持接受的类型：${type}`
        }
    }

    async send(obj) {
        let msg = this.#messageEncode(obj);
        await this._sendMsg(msg);
        let ret = await this._recvMsg();
        return this.#messageDecode(ret);
    }

    static with(cmd, fun) {
        let client = new AsyncClient();
        client.connect(cmd)
            .then(fun)
            .finally(() => client.close()); // 注意不能直接传client.close，不然函数内会找不到this
    }
}
